package redis

import (
	"encoding/json"
	"fmt"
	"github.com/edgexfoundry/edgex-go/internal/app/models"
	"github.com/edgexfoundry/edgex-go/internal/app/pkg/controller/http"
	"github.com/edgexfoundry/edgex-go/internal/app/pkg/utils"
	edgexRedis "github.com/edgexfoundry/edgex-go/internal/pkg/infrastructure/redis"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
	"github.com/gomodule/redigo/redis"
)

// deviceProfileById query device profile by id from DB
func deviceProfileById(conn redis.Conn, deviceId string) (deviceProfile models.DeviceProfile, edgeXerr errors.EdgeX) {
	exists, err := deviceIdExists(conn, deviceId)
	if err != nil {
		return deviceProfile, errors.NewCommonEdgeX(errors.Kind(err), http.DeviceExistCheckFailed, err)
	} else if !exists {
		return deviceProfile, errors.NewCommonEdgeX(errors.KindInvalidId, http.DeviceNotExist, nil)
	}
	storedKey := CreateKey(DeviceCollection, deviceId)
	edgeXerr = getObjectByHash(conn, DeviceProfileNewCollection, storedKey, &deviceProfile)
	if edgeXerr != nil {
		return deviceProfile, errors.NewCommonEdgeX(errors.Kind(edgeXerr), fmt.Sprintf("fail to query device profile by id %s", deviceId), edgeXerr)
	}

	deviceResources, edgeXerr := deviceResourcesGetByDeviceId(conn, deviceId, 0, -1)
	deviceCommands, edgeXerr := deviceCommandGet(conn, deviceId, 0, -1)

	deviceProfile.DeviceResources = deviceResources
	deviceProfile.DeviceCommands = deviceCommands
	return
}

// deviceProfileAdd device profile add
func deviceProfileAdd(conn redis.Conn, deviceId string, d models.DeviceProfile) (string, errors.EdgeX) {
	marshalProfile := d

	profileJSONBytes, errJson := json.Marshal(marshalProfile)
	if errJson != nil {
		return "", errors.NewCommonEdgeX(errors.KindContractInvalid, "unable to JSON marshal device for Redis persistence", errJson)
	}

	if d.Id == "" {
		d.Id, _ = utils.GenerateUUID()
	}

	storedKey := CreateKey(DeviceProfileNewCollection, d.Id)

	err := sendAddDeviceProfileCmd(conn, storedKey, d)
	if err != nil {
		return "", err
	}
	_ = conn.Send(SET, storedKey, profileJSONBytes)
	_ = conn.Send(HSET, DeviceProfileNewCollection, CreateKey(DeviceCollection, deviceId), storedKey)

	_, err = deviceResourceAdd(conn, deviceId, d.DeviceResources...)
	if err != nil {
		return d.Id, errors.NewCommonEdgeXWrapper(err)
	}

	_, err = deviceCommandAdd(conn, deviceId, d.DeviceCommands...)
	if err != nil {
		return d.Id, errors.NewCommonEdgeXWrapper(err)
	}

	return d.Id, nil
}

// sendAddDeviceProfileCmd send redis command for adding device profile
func sendAddDeviceProfileCmd(conn redis.Conn, storedKey string, dp models.DeviceProfile) errors.EdgeX {
	m, err := json.Marshal(dp)
	if err != nil {
		return errors.NewCommonEdgeX(errors.KindContractInvalid, "unable to JSON marshal device profile for Redis persistence", err)
	}
	_ = conn.Send(SET, storedKey, m)
	_ = conn.Send(ZADD, edgexRedis.DeviceProfileCollection, 0, storedKey)
	_ = conn.Send(HSET, edgexRedis.DeviceProfileCollectionName, dp.Name, storedKey)
	_ = conn.Send(ZADD, CreateKey(edgexRedis.DeviceProfileCollectionManufacturer, dp.Manufacturer), dp.Modified, storedKey)
	_ = conn.Send(ZADD, CreateKey(edgexRedis.DeviceProfileCollectionModel, dp.Model), dp.Modified, storedKey)
	for _, label := range dp.Labels {
		_ = conn.Send(ZADD, CreateKey(edgexRedis.DeviceProfileCollectionLabel, label), dp.Modified, storedKey)
	}
	return nil
}

// deviceProfileDelete delete device profile by id from DB
func deviceProfileDelete(conn redis.Conn, deviceId string) errors.EdgeX {

	deviceStoredKey := CreateKey(DeviceCollection, deviceId)

	//delete device profile start
	if profileStoredKey, err := getFieldValueByHash(conn, DeviceProfileNewCollection, deviceStoredKey); err == nil {
		_ = conn.Send(HDEL, DeviceProfileNewCollection, deviceStoredKey)
		_ = conn.Send(DEL, profileStoredKey)
	}

	_ = deleteAllResource(conn, deviceId)
	_ = deleteAllCommands(conn, deviceId)
	return nil
}

func syncDeviceProfileById(conn redis.Conn, deviceId string) (models.DeviceProfile, errors.EdgeX) {
	dp, err := deviceProfileById(conn, deviceId)
	if err != nil {
		return dp, err
	}

	_, err = updateEdgexProfile(conn, deviceId, dp)
	if err != nil {
		return dp, err
	}
	return dp, nil
}

// updateEdgexProfile edgex device profile update
func updateEdgexProfile(conn redis.Conn, deviceId string, d models.DeviceProfile) (string, errors.EdgeX) {
	marshalProfile := d

	profileJSONBytes, errJson := json.Marshal(marshalProfile)
	if errJson != nil {
		return "", errors.NewCommonEdgeX(errors.KindContractInvalid, http.ParamsError, errJson)
	}

	storedKey := CreateKey(DeviceProfileNewCollection, d.Id)

	err := sendAddDeviceProfileCmd(conn, storedKey, d)
	if err != nil {
		return "", err
	}
	_ = conn.Send(SET, storedKey, profileJSONBytes)
	_ = conn.Send(HSET, DeviceProfileNewCollection, CreateKey(DeviceCollection, deviceId), storedKey)

	return d.Id, nil
}
